/**
 * CPU.java
 * A Virtual CPU 
 * @author Will
 */
public class CPU extends Thread{
	//Public Variables
	public int ID;
	protected Process process;
	//Private Variables
	private long[] Register = new long[16];	
	private Fetcher fetch = new Fetcher();
	private Decoder decode = new Decoder();
	private int programCounter = 0, reg1 = 0, reg2 = 0, reg3 = 0, ioCount = 0;
	private long address = 0;
	private boolean halted = false;
	private Instruction instruction = new Instruction();	
	@Override
	public void run(){				
		programCounter = process.ramBaseLocation;
		while(true){
			if(process.size > 0){													//Check to make sure the process isn't a wait command.
				try{
					instruction.setInstruction(fetch.nextInstruction(programCounter));
				}
				catch(NumberFormatException e){
					e.printStackTrace();
					System.out.println("Process Size: " + process.size);
					System.out.println("Process ID  : " + process.number);
					System.out.println("Program CTR : " + programCounter);
					System.out.println("Process RAML: " + process.ramBaseLocation);
					System.out.println("Process DISK: " + process.diskBaseAddress);
					System.out.println("RAM[base]     : " + RAM.read(process.ramBaseLocation));
					System.out.println("DISK[base]    : " + Disk.read(process.diskBaseAddress));
					System.exit(1);
				}
				programCounter++;
				decode.decodeInstruction(instruction);
				prepForExec();
				halted = !(execute());
				if(halted){				
					halted = false;
					Register = new long[16];
					process.numIO = ioCount;				
					ioCount = 0;
					process = STS.Finished(process);					
					if(process == null){					
						//System.out.println("CPU"+ID + " Finished.");
						return;			
					}
					programCounter = process.ramBaseLocation;				
				}
			}
			else{																	//If Process Size = 0, then this process just simply wants the CPU to pause
				try {
					Thread.sleep(1000);												//Pause the CPU for an ammount of time.
					process = STS.Finished(process);
					if(process == null){					
						return;			
					}
					programCounter = process.ramBaseLocation;								
				} 
					catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				} 		
		
			}
		}					
	}


	private void prepForExec() {
		// TODO Auto-generated method stub
		switch(instruction.getType()){
			case ARITHMETIC:
				arithOperation();
				break;
			case BRANCH:
				branchOperation();
				break;
			case JUMP: 
				jumpOperation();
				break;
			case IO:
				ioOperation();
				break;
		}
	}

	private void arithOperation() {
		// TODO Auto-generated method stub
		try{
			reg1 = Convert.binaryToInteger(instruction.getRegister1());
			reg2 = Convert.binaryToInteger(instruction.getRegister2());
			reg3 = Convert.binaryToInteger(instruction.getRegister3());
			address = Convert.binaryToInteger(instruction.getAddress()) / 4;
		}
		catch(NumberFormatException nfe){
			nfe.printStackTrace();
			System.out.println("Error in arithOperation");			
		}		
	}

	private void branchOperation() {		
		try{
			reg1 = Convert.binaryToInteger(instruction.getRegister1());
			reg2 = Convert.binaryToInteger(instruction.getRegister2());
			address = Convert.binaryToInteger(instruction.getAddress());	
			if(address%4 == 0)
				address = address/4;
		}
		catch(NumberFormatException nfe){
			nfe.printStackTrace();
			System.out.println("Error in branchOperation");			
		}		
	}	

	private void jumpOperation() {
		// TODO Auto-generated method stub
		try{
			address = Integer.parseInt(instruction.getAddress()) / 4;
		}
		catch(NumberFormatException nfe){
			System.out.println("Error in jumpOperation");			
		}				
	}	
	
	private void ioOperation() {
		// TODO Auto-generated method stub
		ioCount++;
		try{
			reg1 = Convert.binaryToInteger(instruction.getRegister1());
			reg2 = Convert.binaryToInteger(instruction.getRegister2());
			address = Convert.binaryToInteger(instruction.getAddress()) ;		
			address = address / 4;
		}
		catch(NumberFormatException nfe){
			System.out.println("Error in ioOperation");			
		}			
	}

	private boolean execute(){
		boolean returnValue = true;
		switch(instruction.getOpCode()){
        case RD:
        	Register[reg1] = GetValueAtAddress(Register[reg2] + (address));
            break;
        case WR:        	
            if (address > 0) {            	
                WriteToRam(Register[reg1], address);
            } else {
            	WriteToRam(Register[reg1], Register[reg2]);
            }
            break;
        case ST:
            WriteToRam(Register[reg1], Register[reg2]);
            break;
        case LW:
            Register[reg2] = GetValueAtAddress(Register[reg1]);
            break;
        case MOV:
            Register[reg1] = Register[reg2];
            break;
        case ADD:
            Register[reg3] = Register[reg1] + Register[reg2];
            break;
        case SUB:
            Register[reg3] = Register[reg1] - Register[reg2];
            break;
        case MUL:
            Register[reg3] = Register[reg1] * Register[reg2];
            break;
        case DIV:
            Register[reg3] = Register[reg1] / Register[reg2];
            break;
        case AND:
            Register[reg3] = Register[reg1] & Register[reg2];
            break;
        case OR:
            Register[reg3] = Register[reg1] | Register[reg2];
            break;
        case MOVI:
            Register[reg2] = address;
            break;
        case ADDI:
            Register[reg2] += address;
            break;
        case MULI:
            Register[reg1] = Register[reg1] * GetValueAtAddress(address);
            break;
        case DIVI:
            Register[reg1] = Register[reg1] / GetValueAtAddress(address);
            break;
        case LDI:
            Register[reg2] = Register[reg1] + address;
            break;
        case SLT:
            Register[reg3] = (Register[reg1] < Register[reg2]) ? 1 : 0;
            break;
        case SLTI:
            Register[reg3] = (Register[reg1] < address) ? 1 : 0;
            break;
        case HLT:        	
        	BookKeeper.addResult(process.number-1, new String("CPU"+ID+" Job "+process.number+": " + Register[0]));
        	//System.out.println("CPU"+ID+" Job "+process.number+": " + Register[0]); //Displays the value of the accumulator before halting execution
        	returnValue = false;
            break;
        case NOP:
            break;
        case JMP:
            BranchToAddress(address);
            break;
        case BEQ:
            if (Register[reg1] == Register[reg2]) {
                BranchToAddress(address);
            }
            break;
        case BNE:
            if(process.number == 2)
            {
            	//System.out.println(Register[reg1] + " " + Register[reg2]);
            }
        	if (Register[reg1] != Register[reg2]) {
                BranchToAddress(address);
            }
            break;
        case BEZ:
            if (Register[reg2] == 0) {
                BranchToAddress(address);
            }
            break;
        case BNZ:
            if (Register[reg1] != 0) {
                BranchToAddress(address);
            }
            break;
        case BGZ:
            if (Register[reg1] > 0) {
                BranchToAddress(address);
            }
            break;
        case BLZ:
            if (Register[reg1] < 0) {
                BranchToAddress(address);
            }
            break;
        default:
				System.out.println("Failed to getOpCode");
				return false;
		}
		return returnValue;
	}		
	
	private void WriteToRam(long data, long address2) {
		RAM.write(process.ramBaseLocation + (int)address2, data);
	}
	
	private long GetValueAtAddress(long address) {
		int ramAddress = process.ramBaseLocation + (int)address;
		return Convert.hexToLong(RAM.read(ramAddress)); 
	}
	
	private void BranchToAddress(long address2) {
		programCounter = process.ramBaseLocation + (int) address;
	}



	
}
